iT邦幫忙

2024 iThome 鐵人賽

DAY 17
0

在使用 PowerShell 執行對多個目標的任務時,選擇適當的技巧可以大幅提高效率和性能。今天將深入探討兩種主要的處理方式:

  1. 批次處理 cmdlet:利用專為同時處理多個物件而設計的 cmdlet,可以高效地執行批次任務,減少重複操作。
  2. 物件列舉( object enumeration ):當批次處理無法滿足特定需求時,透過列舉物件逐一處理,提供更大的靈活性,但可能影響執行速度。

首選方式:「批次處理」cmdlet

批次處理 cmdlet 是指那些能夠直接對多個物件進行操作的命令。例如,Get-Service、Stop-Process 等 cmdlet 本身就支援對多個目標( batches, Collections)同時執行操作。這種方法通常是最有效率且簡潔的。

範例:停止名為 "notepad" 的所有進程

Stop-Process -Name "notepad"

範例:將多個檔案複製到目標資料夾

Copy-Item -Path C:\Source\*.txt -Destination C:\Destination\

上述範例中,Stop-Process 和 Copy-Item 都能直接接受多個物件,無需額外的迴圈或列舉。

範例:處理 Pipeline 傳遞的物件時的潛在缺點

情境

我打算用 Get-ChildItem 去搜尋在目前資料夾底下的 txt,並透過 pipeline 傳遞給 Copy-Item 後,Copy-Item 將 Get-ChildItem 的結果 Copy 到子資料夾 temp 底下。先條列出目前資料夾成員及狀態:

PS /Users/kanglin/code/30days> ls -l
total 112
-rw-r--r--@ 1 kanglin  staff   2943  9 26 10:47 Untitled.ipynb
-rw-r--r--@ 1 kanglin  staff     63  9 24 22:14 aliases.txt
-rw-r--r--@ 1 kanglin  staff     90  9 24 13:09 modules.txt
-rw-r--r--@ 1 kanglin  staff  29233  9 26 10:43 result.json
drwxr-xr-x@ 2 kanglin  staff     64 10  1 13:10 temp
-rw-r--r--@ 1 kanglin  staff  11667  9 30 17:01 whitelist.ipynb
PS /Users/kanglin/code/30days> ls -l temp
total 0

透過 Get-ChildItem 搜尋出所有的 txt,並透過 Get-Member 確認其 Type:

PS /Users/kanglin/code/30days> get-ChildItem -Name *.TXT
aliases.txt
modules.txt
PS /Users/kanglin/code/30days> get-ChildItem -Name *.TXT | gm

   TypeName: System.String

當將 Get-ChildItem 的結果透過 pipeline 傳遞給 Copy-Item 操作時,你會發現即使操作完成,也不會有任何結果 output 到畫面上:

PS /Users/kanglin/code/30days> Get-ChildItem -Name *.TXT | Copy-Item -Destination temp
PS /Users/kanglin/code/30days> ls -l temp
total 16
-rw-r--r--@ 1 kanglin  staff  63  9 24 22:14 aliases.txt
-rw-r--r--@ 1 kanglin  staff  90  9 24 13:09 modules.txt

但,你可以透過在 Copy-Item 操作命令中加入 -PassThru 的參數,參數解釋請參考:

get-help copy-item -Parameter Pass*

-PassThru <System.Management.Automation.SwitchParameter>
    Returns an object that represents the item with which you're working. By default, this cmdlet doesn't generate any output.

    Required?                    false
    Position?                    named
    Default value                False
    Accept pipeline input?       False
    Accept wildcard characters?  false

加入後執行結果如下:

PS /Users/kanglin/code/30days> Get-ChildItem -Name *.TXT | Copy-Item -Destination temp -PassThru

    Directory: /Users/kanglin/code/30days/temp

UnixMode         User Group         LastWriteTime         Size Name
--------         ---- -----         -------------         ---- ----
-rw-r--r--    kanglin staff       2024/9/24 22:14           63 aliases.txt
-rw-r--r--    kanglin staff       2024/9/24 13:09           90 modules.txt

備用方案:列舉物件

在某些情況下,我們只能使用會產生物件的 cmdlet,可是卻找不到合適用來傳遞的批次處理 cmdlet,來對這些物件進行一些操作。我們也遇過 cmdlet 不接受來自管線的任何輸出的情況。

當批次處理 cmdlet 無法滿足需求時,可以使用物件列舉的方法,透過迴圈逐一處理每個物件。

範例:逐一停止指定名稱的服務

透過 foreach

$services = Get-Service -Name "Service1", "Service2", "Service3"
foreach ($service in $services) {
    $service.Stop()
}

範例:對每個檔案進行特定的操作

透過 ForEach-Object

Get-ChildItem -Path C:\Logs\*.log | ForEach-Object {
    # 壓縮並移動檔案
    Compress-Archive -Path $_.FullName -DestinationPath "C:\Archive\$($_.Name).zip"
    Remove-Item -Path $_.FullName
}

foreach 與 ForEach-Object 的差異

以下是 foreachForEach-Object 的比較表:

比較項目 foreach ForEach-Object
類型 語法關鍵字 Cmdlet(命令)
行為 先將所有集合載入記憶體,逐項操作 逐一從管線中接收物件並即時處理
資料處理方式 適合處理已完全載入到變數中的集合 適合從管線中逐個處理物件,不需一次載入所有物件
記憶體使用 需要一次載入整個集合,對大數據處理效能較差 不需要一次載入所有物件,適合大數據處理
使用場景 適合處理已存在的集合 適合處理從管線逐個傳入的物件
語法風格 類似傳統迴圈語法 常用於 PowerShell 的管線處理

確認執行時間

透過 Measure-Command cmdlet,可用來測量一個指令碼區塊的執行時間。

解釋:

PS /Users/kanglin/code/30days> get-help Measure-Command

NAME
    Measure-Command

SYNOPSIS
    Measures the time it takes to run script blocks and cmdlets.

使用範例:

將 Get-Process 的結果存到 $allPorcess,目前 Process 數量為 465 個,每次遍例都暫停 0.01 秒,所以看到 Measure-Command 的結果裡呈現出耗時 5 秒是在預期內。

Measure-Command {
    Get-Process | ForEach-Object {
        Write-Host $_.Id
        Start-Sleep 0.01
    }
}

https://ithelp.ithome.com.tw/upload/images/20241001/20168708ZgqJ7e1cwR.png

加快執行速度

為 ForEach-Object 加上 -Parallel ,讓 PowerShell 使用並行方式處理多個物件,從原本的 5 秒,縮短成 1 秒。

Measure-Command {
    Get-Process | ForEach-Object -Parallel {
        Write-Host $_.Id
        Start-Sleep 0.01
    }
}

https://ithelp.ithome.com.tw/upload/images/20241001/20168708Yr4RiaWF4J.png

預設並行限制 (-ThrottleLimit)

預設的並行數量限制是 5。這意味著同時最多會有 5 個平行執行的工作執行緒(threads)。可以使用 -ThrottleLimit 參數來調整這個限制:

Get-Process | ForEach-Object -Parallel {
    Write-Host $_.Id
    Start-Sleep 0.01
} -ThrottleLimit 10  # 調整並行限制為 10

明日主題

Day 18 - CIM vs. WMI


上一篇
Day 16 - 利用背景作業進行多工處理
下一篇
Day 18 - CIM vs. WMI
系列文
《30天挑戰精通 PowerShell:從 Windows Server 到 Azure DevOps 自動化之旅》30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言